/******************************************************************************
* 文件名称: shell.c
* 内容摘要: shell
* 创建者の: 孔佳伟
* 个人主页: https://gitee.com/openes
* 修改记录: 
******************************************************************************/


#include <shell.h>
#include <u_syscall.h>
#include <memory.h>
#include <string.h>

#include <version.h>
#include <clear.h>


#define CMDLINE_POS_X       0                   // 命令行横坐标
#define CMDLINE_POS_Y       8                   // 命令行纵坐标
#define CMDLINE             "Enter command:"    // 命令行提示
#define CMD_MAX             64                  // 命令行最多支持输入 64 个字符
#define CMD_UNKNOWN         "Unknown command:"  // 未知的命令行提示

#define IS_KEYDOWN(key)     (!!(key & 0xFF000000))                          // 判断是否是按键按下
#define GET_CHAR(key)       ({U08 retval = ((key) & 0xFF); retval;})        // 获取键值中的 ASCII
#define GET_VCODE(key)      ({U08 retval = (((key) >> 8) & 0xFF); retval;}) // 获取键值中的虚拟键码

#define VCODE_ENTER         0x0D                // "Enter" 键的虚拟键码
#define VCODE_BACKSPACE     0x08                // "BackSpace" 键的虚拟键码


U08 ShellStack[256] = {0xFF};                   // shell 任务私有栈


static U08 cmd_buf[CMD_MAX] = {0};              // 全局命令行输入字符数组
static U32 cmd_index = 0;                       // 全局命令行输入字符数组下标

static LIST CMD_LIST_HEAD = {0};                // shell 命令链表头

/******************************************************************************
* 函数名称: E_RET CmdRegister(const U08* cmd, CmdFunc func)
* 功能说明: 命令注册
* 输入参数: const U08*  cmd	    --命令 
    　　　　CmdFunc     func    --命令对应的执行函数
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 无
******************************************************************************/
E_RET CmdRegister(const U08* cmd, CmdFunc func)
{
    // 检查参数合法性
    if(NULL == cmd || NULL == func)
        return E_ERR;

    // 申请一个命令节点的内存空间
    SHELL_CMD* shell_cmd = (SHELL_CMD *)Malloc(sizeof(SHELL_CMD));
    if(NULL == shell_cmd)
        return E_ERR;

    shell_cmd->cmd = (U08 *)cmd;
    shell_cmd->func = func;
    ListAddHead(&CMD_LIST_HEAD, &(shell_cmd->node));    // 将命令节点插入命令链表中

    return E_OK;
}

/******************************************************************************
* 函数名称: static void BackSpaceHandle(void)
* 功能说明: 按下 "BackSpace" 键后处理函数
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 删除最后一个字符
******************************************************************************/
static void BackSpaceHandle(void)
{
    if(cmd_index)
    {
        // 首先将光标位置前移一个位置，打印 " " 空格字符，打印完成后光标位置又向后偏移了一个位置，需要重新设置回来
        cmd_index--;
        SetCursorPos(sizeof(CMDLINE) -1 + cmd_index, CMDLINE_POS_Y);
        print(" ");
        SetCursorPos(sizeof(CMDLINE) -1 + cmd_index, CMDLINE_POS_Y);
    }
}

/******************************************************************************
* 函数名称: static void ResetCmdLine(void)
* 功能说明: 复位命令行
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
static void ResetCmdLine(void)
{
    U32 i = 0;

    cmd_index = 0;
    SetCursorPos(sizeof(CMDLINE) -1, CMDLINE_POS_Y);
    for(i = 0; i < CMD_MAX; i++)
        print(" ");
    SetCursorPos(sizeof(CMDLINE) -1, CMDLINE_POS_Y);
}

/******************************************************************************
* 函数名称: static E_RET DoCmd(U08* cmd)
* 功能说明: 查找并执行命令
* 输入参数: U08* cmd	--命令 
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 无
******************************************************************************/
static E_RET DoCmd(U08* cmd)
{
    LIST_NODE* pListNode = NULL;
    SHELL_CMD* nodeTmp = NULL;

    // 遍历命令链表
    LIST_FOR_EACH(&CMD_LIST_HEAD, pListNode)
    {
        nodeTmp = (SHELL_CMD *)LIST_NODE(pListNode, SHELL_CMD, node);
        if(StrCmp(nodeTmp->cmd, cmd, -1))           // 找到命令
        {
            nodeTmp->func();
            ResetCmdLine();
            return E_OK;
        }
    }

    return E_ERR;
}

/******************************************************************************
* 函数名称: static void BackSpaceHandle(void)
* 功能说明: 按下 "Enter" 键后处理函数
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
static void EnterHandle(void)
{
    U32 i = 0;

    // 输入的命令行字符为空，则直接退出
    if(0 == cmd_index)
        return;

    cmd_buf[cmd_index] = 0;                     // 先在输入的字符串最后添加字符串结束标志 '\0'

    // 在开始解析命令之前，先将命令行的下一行清空，用于接下来的命令提示
    SetCursorPos(CMDLINE_POS_X, CMDLINE_POS_Y+1);
    for(i = 0; i < sizeof(CMD_UNKNOWN) -1 + CMD_MAX; i++)
        print(" ");

    SetCursorPos(CMDLINE_POS_X, CMDLINE_POS_Y+1);

    // 如果命令执行失败，则打印 "Unknown command:xxx"
    if(E_ERR == DoCmd(cmd_buf))
    {
        print(CMD_UNKNOWN);
        print("%s", cmd_buf);
        ResetCmdLine();                         // 复位命令行
    }
}

/******************************************************************************
* 函数名称: static void CmdLineHandle(U08 ascii, U08 vcode)
* 功能说明: 命令行处理
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
static void CmdLineHandle(U08 ascii, U08 vcode)
{
    // 命令行输入显示
    if(ascii)
    {
        if(cmd_index < CMD_MAX)
        {
            cmd_buf[cmd_index++] = ascii;
            print("%c", ascii);
        }
    }

    switch(vcode)
    {
        case VCODE_BACKSPACE:   // 按下 "BackSpace" 键
            BackSpaceHandle();
            break;
        case VCODE_ENTER:       // 按下 "Enter" 键
            EnterHandle();
            break;
        default:
            break;
    }
}

/******************************************************************************
* 函数名称: void ShellTask(void)
* 功能说明: shell 任务
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
void ShellTask(void)  
{
    U32 key = 0;
    U08 ascii = 0;
    U08 vcode = 0;

    // 固定命令行的显示位置，并打印提示字符 "Enter command:"
    SetCursorPos(CMDLINE_POS_X, CMDLINE_POS_Y);
    print(CMDLINE);

    ListInit(&CMD_LIST_HEAD);                   // 初始化 shell 命令链表

    CmdRegister(CMD_VERSION, (CmdFunc)KernelVersion);   // 注册命令
    CmdRegister(CMD_CLEAR, (CmdFunc)Clear);             // 注册命令

    while (1)
    {
        key = ReadKey(); 
        if(IS_KEYDOWN(key))                     // 如果是按键按下 
        {
            ascii = GET_CHAR(key);              // 获取键值中的 ASCII
            vcode = GET_VCODE(key);             // 获取键值中的虚拟键码
            CmdLineHandle(ascii, vcode);        // 命令行处理
        }
    }
}
